/*
 * Copyright (C) 2010 TTPASI, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ttpasi.jgenere.initial_port;

import static ttpasi.jgenere.initial_port.Utils.isAlpha;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ttpasi.jgenere.initial_port.languagedata.LanguageData;


/**
 * Java port of VigCrack from PyGenere v 0.3.  See pygenere.py to learn more 
 * about the original PyGenere implementation, including author contact 
 * information and licensing and use terms.
 * <p/>
 * VigCrack objects can crack some cipher keys and messages of Vigenere 
 * enciphered messages.  Longer messages are more likely crackable by this 
 * algorithm than shorter messages.
 * <p/>
 * VigCrack objects are immutable.
 * <p/>
 * Note: This implementation only supports characters in the English alphabet, 
 * though multiple languages are supported.
 * <p/>
 * This typical use sequence cracks a Vigenere encryption key 
 * <pre>
 * VigCrack vc = new VigCrack(_codedtext);
 * String codeword = vc.crackCodeword(new String[] {"5", "9"});
 * </pre>
 * This typical use sequence cracks a Vigenere encryption key and decrypts the 
 * encrypted message
 * <pre>
 * VigCrack vc = new VigCrack(_codedtext);
 * String message = vc.crackMessage(new String[] {"5", "9"});
 * </pre>
 * See jgenere.initial_port.VigCrackTest source code for further example uses, 
 * including how to use VigCrack with different languages.
 * <p/>
 * Note: While the cryptography implementations in jgenere.initial_port work 
 * correctly according to the provided test cases, they were not optimized or 
 * otherwise designed to make use of Java language and platform features.  They 
 * are simple ports of the original Python implementation of PyGenere, provided 
 * for academic purposes.  Visit the project web site at 
 * <a target="_blank" href="http://code.google.com/p/jgenere/">http://code.google.com/p/jgenere/</a> 
 * for preferred Java-based implementations of these cryptography algorithms.
 * <p/>
 * The original PyGenere v 0.3 VigCrack code:
 * <pre>
 * class VigCrack(Vigenere):
 * 
 *   """
 *   VigCrack objects have methods to break Vigenere-encoded texts when the
 *   original key is unknown.
 * 
 *   The technique used is based on the one described in:
 * 
 *   http: * www.stonehill.edu/compsci/Shai_papers/RSA.pdf
 *   (pages 9-10)
 * 
 *   Character frequencies taken from:
 *   http: * www.csm.astate.edu/~rossa/datasec/frequency.html (English)
 *   http: * www.characterfrequency.com/ (French, Italian, Portuguese, Spanish)
 *   http: * www.santacruzpl.org/readyref/files/g-l/ltfrqger.shtml (German)
 *   """
 * 
 *   # Unless otherwise specified, test for codewords between (and including)
 *   # these two lengths:
 *   __default_min_codeword_length = 5
 *   __default_max_codeword_length = 9
 * 
 *   # The following are language-specific data on character frequencies.
 *   # Kappa is the "index of coincidence" described in the cryptography paper
 *   # (link above).
 *   __english_data = {
 *                     'A':8.167, 'B':1.492, 'C':2.782, 'D':4.253, 'E':12.702,
 *                     'F':2.228, 'G':2.015, 'H':6.094, 'I':6.996, 'J':0.153,
 *                     'K':0.772, 'L':4.025, 'M':2.406, 'N':6.749, 'O':7.507,
 *                     'P':1.929, 'Q':0.095, 'R':5.987, 'S':6.327, 'T':9.056,
 *                     'U':2.758, 'V':0.978, 'W':2.360, 'X':0.150, 'Y':1.974,
 *                     'Z':0.074, 'max_val':12.702, 'kappa':0.0667
 *                    }
 * 
 *   __french_data = {
 *                    'A':8.11, 'B':0.903, 'C':3.49, 'D':4.27, 'E':17.22,
 *                    'F':1.14, 'G':1.09, 'H':0.769, 'I':7.44, 'J':0.339,
 *                    'K':0.097, 'L':5.53, 'M':2.89, 'N':7.46, 'O':5.38,
 *                    'P':3.02, 'Q':0.999, 'R':7.05, 'S':8.04, 'T':6.99,
 *                    'U':5.65, 'V':1.30, 'W':0.039, 'X':0.435, 'Y':0.271,
 *                    'Z':0.098, 'max_val':17.22, 'kappa':0.0746
 *                   }
 * 
 *   __german_data = {
 *                    'A':6.506, 'B':2.566, 'C':2.837, 'D':5.414, 'E':16.693,
 *                    'F':2.044, 'G':3.647, 'H':4.064, 'I':7.812, 'J':0.191,
 *                    'K':1.879, 'L':2.825, 'M':3.005, 'N':9.905, 'O':2.285,
 *                    'P':0.944, 'Q':0.055, 'R':6.539, 'S':6.765, 'T':6.742,
 *                    'U':3.703, 'V':1.069, 'W':1.396, 'X':0.022, 'Y':0.032,
 *                    'Z':1.002, 'max_val':16.693, 'kappa':0.0767
 *                   }
 * 
 *   __italian_data = {
 *                     'A':11.30, 'B':0.975, 'C':4.35, 'D':3.80, 'E':11.24,
 *                     'F':1.09, 'G':1.73, 'H':1.02, 'I':11.57, 'J':0.035,
 *                     'K':0.078, 'L':6.40, 'M':2.66, 'N':7.29, 'O':9.11,
 *                     'P':2.89, 'Q':0.391, 'R':6.68, 'S':5.11, 'T':6.76,
 *                     'U':3.18, 'V':1.52, 'W':0.00, 'X':0.024, 'Y':0.048,
 *                     'Z':0.958, 'max_val':11.57, 'kappa':0.0733
 *                    }
 * 
 *   __portuguese_data = {
 *                        'A':13.89, 'B':0.980, 'C':4.18, 'D':5.24, 'E':12.72,
 *                        'F':1.01, 'G':1.17, 'H':0.905, 'I':6.70, 'J':0.317,
 *                        'K':0.0174, 'L':2.76, 'M':4.54, 'N':5.37, 'O':10.90,
 *                        'P':2.74, 'Q':1.06, 'R':6.67, 'S':7.90, 'T':4.63,
 *                        'U':4.05, 'V':1.55, 'W':0.0104, 'X':0.272, 'Y':0.0165,
 *                        'Z':0.400, 'max_val':13.89, 'kappa':0.0745
 *                       }
 * 
 *   __spanish_data = {
 *                     'A':12.09, 'B':1.21, 'C':4.20, 'D':4.65, 'E':13.89,
 *                     'F':0.642, 'G':1.11, 'H':1.13, 'I':6.38, 'J':0.461,
 *                     'K':0.038, 'L':5.19, 'M':2.86, 'N':7.23, 'O':9.58,
 *                     'P':2.74, 'Q':1.37, 'R':6.14, 'S':7.43, 'T':4.49,
 *                     'U':4.53, 'V':1.05, 'W':0.011, 'X':0.124, 'Y':1.14,
 *                     'Z':0.324, 'max_val':13.89, 'kappa':0.0766
 *                    }
 * 
 *   # The default language is set to English.
 *   __lang = 'EN'
 *   __lang_data = __english_data
 * 
 *   # This method sets the lang (__lang) attribute of a VigCrack object.
 *   def set_language(self, language):
 *       self.__lang = language.upper()
 *       if self.__lang == 'DE':
 *           self.__lang_data = self.__german_data
 *       elif self.__lang == 'ES':
 *           self.__lang_data = self.__spanish_data
 *       elif self.__lang == 'FR':
 *           self.__lang_data = self.__french_data
 *       elif self.__lang == 'IT':
 *           self.__lang_data = self.__italian_data
 *       elif self.__lang == 'PT':
 *           self.__lang_data = self.__portuguese_data
 *       else:
 *           self.__lang = 'EN'
 *       return self
 * 
 *   # Rotate text n places to the right, wrapping around at the end.
 *   def __rotate_right(self, n):
 *       cutting_point = len(self) - (n % len(self))
 *       return self[cutting_point:] + self[:cutting_point]
 * 
 *   # Get every nth char from a piece of text, from a given starting position.
 *   def __get_every_nth_char(self, start, n):
 *       accumulator = []
 *       for i in range(len(self)):
 *           if (i % n) == start:
 *               accumulator.append(self[i])
 *       return VigCrack(''.join(accumulator)).set_language(self.__lang)
 * 
 *   # Build a dictionary containing the number of occurrences of each char.
 *   def __count_char_freqs(self):
 *       dictionary = {}
 *       self = self.upper()
 *       for char in self:
 *           if char.isalpha():
 *               dictionary[char] = dictionary.get(char, 0) + 1
 *       return dictionary
 * 
 *   # Scale the dictionary so that it can be compared with __lang_data.
 *   def __scale(self, dictionary):
 *       v = dictionary.values()
 *       v.sort()
 *       max_val = v[-1]
 *       scaling_factor = self.__lang_data['max_val']/max_val
 *       for (k, v) in dictionary.items():
 *           dictionary[k] = v*scaling_factor
 *       return dictionary
 * 
 *   # The residual error is the difference between a char's frequency in
 *   # __lang_data and its frequency in the scaled dictionary from above.
 *   # The error is then squared to remove a possible negative value.
 *   def __sum_residuals_squared(self, dictionary):
 *       sum = 0
 *       for (k, v) in dictionary.items():
 *           sum += (v - self.__lang_data[k])**2
 *       return sum
 * 
 *   # Find the Caesar shift that brings the ciphertext closest to the
 *   # character distribution of the plaintext's language.
 *   def __find_best_caesar_shift(self):
 *       best = 0
 *       smallest_sum = -1
 *       # Find the residual sum for each shift.
 *       for shift in range(26):
 *           encoded_text = Caesar(self).encipher(shift)
 *           vigcrack_obj = VigCrack(encoded_text).set_language(self.__lang)
 *           char_freqs = vigcrack_obj.__count_char_freqs()
 *           scaled = vigcrack_obj.__scale(char_freqs)
 *           current_sum = vigcrack_obj.__sum_residuals_squared(scaled)
 *           # Keep track of the shift with the lowest residual sum.
 *           # If there's a tie, the smallest shift wins.
 *           if smallest_sum == -1:
 *               smallest_sum = current_sum
 *           if current_sum < smallest_sum:
 *               best = shift
 *               smallest_sum = current_sum
 *       return best
 * 
 *   def __find_codeword_length(self, min_length, max_length):
 *       codeword_length = min_length
 *       kappas = []
 *       # Put the kappa value for each codeword length tested into an array.
 *       for i in range(min_length, max_length + 1):
 *           temp = self.__rotate_right(i)
 *           coincidences = 0
 *           for j in range(len(self)):
 *               if temp[j] == self[j]:
 *                   coincidences += 1
 *           kappas.append(float(coincidences)/len(self))
 *       # Find out which value of kappa is closest to the kappa of the
 *       # plaintext's language.  If there's a tie, the shortest codeword wins.
 *       smallest_squared_diff = -1
 *       for i in range((max_length + 1) - min_length):
 *           current_squared_diff = (self.__lang_data['kappa'] - kappas[i])**2
 *           if smallest_squared_diff == -1:
 *               smallest_squared_diff = current_squared_diff
 *           if current_squared_diff < smallest_squared_diff:
 *               codeword_length = min_length + i
 *               smallest_squared_diff = current_squared_diff
 *       return codeword_length
 * 
 *   def __find_codeword(self, min_length, max_length):
 *       # Strip away invalid chars.
 *       accumulator = []
 *       for char in self:
 *           if char.isalpha():
 *               accumulator.append(char)
 *       alpha_only = VigCrack(''.join(accumulator)).set_language(self.__lang)
 *       codeword_length = alpha_only.__find_codeword_length(min_length,
 *                                                           max_length)
 *       # Build the codeword by finding one character at a time.
 *       codeword = []
 *       for i in range(codeword_length):
 *           temp = alpha_only.__get_every_nth_char(i, codeword_length)
 *           shift = temp.__find_best_caesar_shift()
 *           if shift == 0:
 *               codeword.append('A')
 *           else:
 *               codeword.append(chr(ord('A') + (26 - shift)))
 *       return VigCrack(''.join(codeword)).set_language(self.__lang)
 * 
 *   def __parse_args(self, *arg_list):
 *       if len(arg_list) == 0:    # Use default values for codeword length.
 *           min_length = self.__default_min_codeword_length
 *           max_length = self.__default_max_codeword_length
 *       elif len(arg_list) == 1:    # Exact codeword length specified by user.
 *           min_length = max_length = int(arg_list[0])
 *       else:    # min_length and max_length given by user.
 *           min_length = int(arg_list[0])
 *           max_length = int(arg_list[1])
 *       # Check for input errors.
 *       if min_length == max_length:
 *           if min_length < 1:
 *               raise InputError('Codeword length is too small')
 *       else:
 *           if min_length < 1:
 *               raise InputError('min_length is too small')
 *           if max_length < 1:
 *               raise InputError('max_length is too small')
 *       if max_length < min_length:
 *           raise InputError('max_length cannot be shorter than min_length')
 *       if len(self) == 0:
 *           raise InputError('Ciphertext is empty')
 *       if len(self) < max_length:
 *           raise InputError('Ciphertext is too short')
 *       # Check that the ciphertext contains at least one valid character.
 *       has_valid_char = False
 *       for char in self:
 *           if char.isalpha():
 *               has_valid_char = True
 *               break
 *       if not has_valid_char:
 *           raise InputError('No valid characters in ciphertext')
 *       # If everything's all right, return the min_length and max_length.
 *       return [min_length, max_length]
 * 
 *   def crack_codeword(self, *arg_list):
 *       """
 *       Try to find the codeword that encrypted the ciphertext object.
 *       If no arguments are supplied, codewords between the default minimum
 *       length and the default maximum length are tried.
 *       If one integer argument is supplied, only codewords with that length
 *       will be tried.
 *       If two integer arguments are given then the first argument is treated
 *       as a minimum codeword length, and the second argument is treated as a
 *       maximum codeword length, to try.
 *       """
 *       array = self.__parse_args(*arg_list)
 *       return self.__find_codeword(array[0], array[1])
 * 
 *   def crack_message(self, *arg_list):
 *       """
 *       Try to decode the ciphertext object.
 *       This method accepts arguments in the same way as the crack_codeword()
 *       method.
 *       """
 *       codeword = self.crack_codeword(*arg_list)
 *       return self.decipher(codeword)
 * </pre>
 * 
 * @author  Bruce [com.gmail @ ProgrammerBruce]
 * @version 2010.1105
 */
//class VigCrack(Vigenere):
public class VigCrack
{
  private final String _text;

  /**
   * Constructs a new VigCrack with an encrypted text message to be deciphered.
   * <p/>
   * Assumes the message to be deciphered is in English. See 
   * {@link #VigCrack(String, String)} for other languages that can be used.
   * 
   * @param message The text message to decipher.
   */
  public VigCrack(String message)
  {
    this(message, "EN");
  }

  /**
   * Constructs a new VigCrack with an encrypted text message, in a user 
   * specified language, to be deciphered.
   * <p/>
   * <code>language</code> is a two character String that indicates which 
   * language to use. Possible values for <code>language</code> include:
   * <ul>
   * <li>EN - for English</li>
   * <li>DE - for German</li>
   * <li>ES - for Spanish</li>
   * <li>FR - for French</li>
   * <li>IT - for Italian</li>
   * <li>PT - for Portuguese</li>
   * </ul>
   * 
   * @param message The text message to decipher.
   * @param language The language of the message.
   */
  public VigCrack(String message, String language)
  {
    if (message == null)
    {
      message = "";
    }
    _text = message;

    if (language == null)
    {
      language = "";
    }
    language = language.toUpperCase();
    _lang_data = LanguageData.getData(language);
    _lang = language;
  }

//  # Unless otherwise specified, test for codewords between (and including)
//  # these two lengths:
//  __default_min_codeword_length = 5
//  __default_max_codeword_length = 9

  private static final int _default_min_codeword_length = 5;
  private static final int _default_max_codeword_length = 9;

//  # The default language is set to English.
//  __lang = 'EN'
//  __lang_data = __english_data
  
  private final String _lang;
  private final LanguageData _lang_data;

//  # This method sets the lang (__lang) attribute of a VigCrack object.
//  def set_language(self, language):
//  self.__lang = language.upper()
//  if self.__lang == 'DE':
//    self.__lang_data = self.__german_data
//  elif self.__lang == 'ES':
//    self.__lang_data = self.__spanish_data
//  elif self.__lang == 'FR':
//    self.__lang_data = self.__french_data
//  elif self.__lang == 'IT':
//    self.__lang_data = self.__italian_data
//  elif self.__lang == 'PT':
//    self.__lang_data = self.__portuguese_data
//  else:
//    self.__lang = 'EN'
//  return self
  /*
   * Only used when new VigCrack instances constructed.  So, moved to 
   * constructor.
   */
//  private VigCrack setLanguage(String language)
//  {
//    language = language.toUpperCase();
//    if (language.equals("DE"))
//      _lang_data = new GermanData();
//    else if (language.equals("ES"))
//      _lang_data = new SpanishData();
//    else if (language.equals("FR"))
//      _lang_data = new FrenchData();
//    else if (language.equals("IT"))
//      _lang_data = new ItalianData();
//    else if (language.equals("PT"))
//      _lang_data = new PortugeseData();
//    else
//      language = "EN";
//
//    _lang = language;
//    
//    return this;
//  }
  
//  # Rotate text n places to the right, wrapping around at the end.
//  def __rotate_right(self, n):
//      cutting_point = len(self) - (n % len(self))
//      return self[cutting_point:] + self[:cutting_point]
  private VigCrack rotateRight(int n)
  {
    int cutting_point = _text.length() - (n % _text.length());
    String text = _text.substring(cutting_point) + _text.substring(0, cutting_point);
    return new VigCrack(text, _lang);
  }
  
//  # Get every nth char from a piece of text, from a given starting position.
//  def __get_every_nth_char(self, start, n):
//      accumulator = []
//      for i in range(len(self)):
//          if (i % n) == start:
//              accumulator.append(self[i])
//      return VigCrack(''.join(accumulator)).set_language(self.__lang)
  private VigCrack getEveryNthChar(int start, int n)
  {
    StringBuilder accumulator = new StringBuilder();
    for (int i = 0; i < _text.length(); i++)
    {
      if ((i % n) == start)
      {
        accumulator.append(_text.charAt(i));
      }
    }
    return new VigCrack(accumulator.toString(), _lang);
  }
  
//  # Build a dictionary containing the number of occurrences of each char.
//  def __count_char_freqs(self):
//      dictionary = {}
//      self = self.upper()
//      for char in self:
//          if char.isalpha():
//              dictionary[char] = dictionary.get(char, 0) + 1
//      return dictionary
  private Map<Character, Double> countCharFreqs()
  {
    Map<Character, Double> dictionary = new HashMap<Character, Double>();
    String text = _text.toUpperCase(); // Didn't change this VigCrack, because not necessary, and may be an unexpected side effect.
    for (char c : text.toCharArray())
    {
      if (isAlpha(c))
      {
        Double i = dictionary.get(c);
        i = (i == null) ? 0 : i;
        dictionary.put(c, i + 1);
      }
    }
    return dictionary;
  }
  
//  # Scale the dictionary so that it can be compared with __lang_data.
//  def __scale(self, dictionary):
//      v = dictionary.values()
//      v.sort()
//      max_val = v[-1]
//      scaling_factor = self.__lang_data['max_val']/max_val
//      for (k, v) in dictionary.items():
//          dictionary[k] = v*scaling_factor
//      return dictionary
  private Map<Character, Double> scale(Map<Character, Double> dictionary)
  {
    List<Double> v = new ArrayList<Double>(dictionary.values());
    Collections.sort(v);
    double max_val = v.get(v.size() - 1);
    double scaling_factor = _lang_data._max_value / max_val;
    for (Character k : dictionary.keySet())
    {
      dictionary.put(k, dictionary.get(k) * scaling_factor);
    }
    return dictionary;
  }
  
//  # The residual error is the difference between a char's frequency in
//  # __lang_data and its frequency in the scaled dictionary from above.
//  # The error is then squared to remove a possible negative value.
//  def __sum_residuals_squared(self, dictionary):
//      sum = 0
//      for (k, v) in dictionary.items():
//          sum += (v - self.__lang_data[k])**2
//      return sum
  private double sumResidualsSquared(Map<Character, Double> dictionary)
  {
    double sum = 0;
    for (Character k : dictionary.keySet())
    {
      Double v = dictionary.get(k);
      sum += (v - _lang_data._frequencies.get(k)) * (v - _lang_data._frequencies.get(k));
    }
    return sum;
  }
  
//  # Find the Caesar shift that brings the ciphertext closest to the
//  # character distribution of the plaintext's language.
//  def __find_best_caesar_shift(self):
//      best = 0
//      smallest_sum = -1
//      # Find the residual sum for each shift.
//      for shift in range(26):
//          encoded_text = Caesar(self).encipher(shift)
//          vigcrack_obj = VigCrack(encoded_text).set_language(self.__lang)
//          char_freqs = vigcrack_obj.__count_char_freqs()
//          scaled = vigcrack_obj.__scale(char_freqs)
//          current_sum = vigcrack_obj.__sum_residuals_squared(scaled)
//          # Keep track of the shift with the lowest residual sum.
//          # If there's a tie, the smallest shift wins.
//          if smallest_sum == -1:
//              smallest_sum = current_sum
//          if current_sum < smallest_sum:
//              best = shift
//              smallest_sum = current_sum
//      return best
  private int findBestCaesarShift()
  {
    int best = 0;
    double smallest_sum = -1D;
    for (int shift = 0; shift < 26; shift++)
    {
      String encoded_text = new Caesar(_text).encipher(shift).text();
      VigCrack vigcrack_obj = new VigCrack(encoded_text, _lang);
      Map<Character, Double> char_freqs = vigcrack_obj.countCharFreqs();
      Map<Character, Double> scaled = vigcrack_obj.scale(char_freqs);
      double current_sum = vigcrack_obj.sumResidualsSquared(scaled);

      if (smallest_sum == -1)
      {
        smallest_sum = current_sum;
      }
      if (current_sum < smallest_sum)
      {
        best = shift;
        smallest_sum = current_sum;
      }
    }
    return best;
  }
  
//  def __find_codeword_length(self, min_length, max_length):
//      codeword_length = min_length
//      kappas = []
//      # Put the kappa value for each codeword length tested into an array.
//      for i in range(min_length, max_length + 1):
//          temp = self.__rotate_right(i)
//          coincidences = 0
//          for j in range(len(self)):
//              if temp[j] == self[j]:
//                  coincidences += 1
//          kappas.append(float(coincidences)/len(self))
//      # Find out which value of kappa is closest to the kappa of the
//      # plaintext's language.  If there's a tie, the shortest codeword wins.
//      smallest_squared_diff = -1
//      for i in range((max_length + 1) - min_length):
//          current_squared_diff = (self.__lang_data['kappa'] - kappas[i])**2
//          if smallest_squared_diff == -1:
//              smallest_squared_diff = current_squared_diff
//          if current_squared_diff < smallest_squared_diff:
//              codeword_length = min_length + i
//              smallest_squared_diff = current_squared_diff
//      return codeword_length
  private int findCodewordLength(int min_length, int max_length)
  {
    int codeword_length = min_length;
    List<Double> kappas = new ArrayList<Double>();

    for (int i = min_length; i < max_length + 1; i++)
    {
      VigCrack temp = rotateRight(i);
      int coincidences = 0;
      for (int j = 0; j < _text.length(); j++)
      {
        if (temp._text.charAt(j) == _text.charAt(j))
        {
          coincidences += 1;
        }
      }
      kappas.add(((double)coincidences) / _text.length());
    }

    double smallest_squared_diff = -1D;
    for (int i = 0; i < max_length + 1 - min_length; i++)
    {
      double current_squared_diff = (_lang_data._kappa - kappas.get(i)) * (_lang_data._kappa - kappas.get(i));
      if (smallest_squared_diff == -1)
      {
        smallest_squared_diff = current_squared_diff;
      }
      if (current_squared_diff < smallest_squared_diff)
      {
        codeword_length = min_length + i;
        smallest_squared_diff = current_squared_diff;
      }
    }
    
    return codeword_length;
  }
  
//  def __find_codeword(self, min_length, max_length):
//      # Strip away invalid chars.
//      accumulator = []
//      for char in self:
//          if char.isalpha():
//              accumulator.append(char)
//      alpha_only = VigCrack(''.join(accumulator)).set_language(self.__lang)
//      codeword_length = alpha_only.__find_codeword_length(min_length,
//                                                          max_length)
//      # Build the codeword by finding one character at a time.
//      codeword = []
//      for i in range(codeword_length):
//          temp = alpha_only.__get_every_nth_char(i, codeword_length)
//          shift = temp.__find_best_caesar_shift()
//          if shift == 0:
//              codeword.append('A')
//          else:
//              codeword.append(chr(ord('A') + (26 - shift)))
//      return VigCrack(''.join(codeword)).set_language(self.__lang)
  private String findCodeword(int min_length, int max_length)
  {
    StringBuilder accumulator = new StringBuilder();
    for (char c : _text.toCharArray())
    {
      if (isAlpha(c))
      {
        accumulator.append(c);
      }
    }
    VigCrack alpha_only = new VigCrack(accumulator.toString(), _lang);
    int codeword_length = alpha_only.findCodewordLength(min_length, max_length);

    StringBuilder codeword = new StringBuilder();
    for (int i = 0; i < codeword_length; i++)
    {
      VigCrack temp = alpha_only.getEveryNthChar(i, codeword_length);
      int shift = temp.findBestCaesarShift();
      if (shift == 0)
      {
        codeword.append('A');
      }
      else
      {
        codeword.append((char)(((int)'A') + (26 - shift)));
      }
    }
    return codeword.toString();
  }
  
//  def __parse_args(self, *arg_list):
//      if len(arg_list) == 0:    # Use default values for codeword length.
//          min_length = self.__default_min_codeword_length
//          max_length = self.__default_max_codeword_length
//      elif len(arg_list) == 1:    # Exact codeword length specified by user.
//          min_length = max_length = int(arg_list[0])
//      else:    # min_length and max_length given by user.
//          min_length = int(arg_list[0])
//          max_length = int(arg_list[1])
//      # Check for input errors.
//      if min_length == max_length:
//          if min_length < 1:
//              raise InputError('Codeword length is too small')
//      else:
//          if min_length < 1:
//              raise InputError('min_length is too small')
//          if max_length < 1:
//              raise InputError('max_length is too small')
//      if max_length < min_length:
//          raise InputError('max_length cannot be shorter than min_length')
//      if len(self) == 0:
//          raise InputError('Ciphertext is empty')
//      if len(self) < max_length:
//          raise InputError('Ciphertext is too short')
//      # Check that the ciphertext contains at least one valid character.
//      has_valid_char = False
//      for char in self:
//          if char.isalpha():
//              has_valid_char = True
//              break
//      if not has_valid_char:
//          raise InputError('No valid characters in ciphertext')
//      # If everything's all right, return the min_length and max_length.
//      return [min_length, max_length]
  private int[] parseArgs(String[] arg_list)
  {
    int min_length = _default_min_codeword_length;
    int max_length = _default_max_codeword_length;
    if (arg_list != null)
    {
      if (arg_list.length == 1)
      {
        min_length = Integer.parseInt(arg_list[0]);
        max_length = min_length;
      }
      else if (arg_list.length > 1)
      {
        min_length = Integer.parseInt(arg_list[0]);
        max_length = Integer.parseInt(arg_list[1]);
      }
    }

    if (min_length == max_length)
    {
      if (min_length < 1)
      {
        throw new InputError("Codeword length is too small");
      }
    }
    else
    {
      if (min_length < 1)
      {
        throw new InputError("min_length is too small");
      }
      if (max_length < 1)
      {
        throw new InputError("max_length is too small");
      }
    }
    if (max_length < min_length)
    {
      throw new InputError("max_length cannot be shorter than min_length");
    }
    if (_text.length() == 0)
    {
      throw new InputError("Ciphertext is empty");
    }
    if (_text.length() < max_length)
    {
      throw new InputError("Ciphertext is too short");
    }

    boolean has_valid_char = false;
    for (char c : _text.toCharArray())
    {
      if (isAlpha(c))
      {
        has_valid_char = true;
        break;
      }
    }
    if (!has_valid_char)
    {
      throw new InputError("No valid characters in ciphertext");
    }
    return new int[] {min_length, max_length};
  }

  /**
   * Attempts to discover the key used to encrypt the internal message.
   * <p/>
   * If the provided arg_list is null or empty, then keys with lengths in the 
   * range of 5 to 9 (inclusive) are used. Otherwise, arg_list should have  
   * either one or two components, and the components should be String 
   * representations of integral numbers, e.g., "5", "6". If only one component 
   * is provided, only keys of that length are sought.  If two components are 
   * provided, then keys with lengths in the range represented by the two 
   * components are sought.  For example, if arg_list is {"3", "5"}, then keys 
   * with lengths 3, 4, and 5 are sought. The one key that best decrypts the 
   * internal encrypted message is used.
   * 
   * @param     arg_list Optional length range within which to look for the key  
   *            used to encrypt the internal message.
   * @return    The key used to encrypt the internal encrypted message.
   */
//  def crack_codeword(self, *arg_list):
//      """
//      Try to find the codeword that encrypted the ciphertext object.
//      If no arguments are supplied, codewords between the default minimum
//      length and the default maximum length are tried.
//      If one integer argument is supplied, only codewords with that length
//      will be tried.
//      If two integer arguments are given then the first argument is treated
//      as a minimum codeword length, and the second argument is treated as a
//      maximum codeword length, to try.
//      """
//      array = self.__parse_args(*arg_list)
//      return self.__find_codeword(array[0], array[1])
  public String crackCodeword(String[] arg_list)
  {
    int[] array = parseArgs(arg_list);
    return findCodeword(array[0], array[1]);
  }

  /**
   * Attempts to discover the plain text message of the internal encrypted 
   * message.
   * <p/>
   * If the provided arg_list is null or empty, then keys with lengths in the 
   * range of 5 to 9 (inclusive) are used. Otherwise, arg_list should have  
   * either one or two components, and the components should be String 
   * representations of integral numbers, e.g., "5", "6". If only one component 
   * is provided, only keys of that length are sought.  If two components are 
   * provided, then keys with lengths in the range represented by the two 
   * components are sought.  For example, if arg_list is {"3", "5"}, then keys 
   * with lengths 3, 4, and 5 are sought. The one key that best decrypts the 
   * internal encrypted message is used. 
   * 
   * @param     arg_list Optional length range within which to look for the key  
   *            used to encrypt the internal message.
   * @return    The plain text message, decrypted from the internal encrypted 
   *            message.
   */
//  def crack_message(self, *arg_list):
//      """
//      Try to decode the ciphertext object.
//      This method accepts arguments in the same way as the crack_codeword()
//      method.
//      """
//      codeword = self.crack_codeword(*arg_list)
//      return self.decipher(codeword)
  public String crackMessage(String[] arg_list)
  {
    String codeword = crackCodeword(arg_list);
    return new Vigenere(_text).decipher(codeword).text();
  }

  /**
   * Retrieves the internal English alphabet text message.
   * 
   * @return The internal English alphabet text message.
   */
  public String text()
  {
    return _text;
  }

  /**
   * Retrieves the internal English alphabet text message.  Same behavior as 
   * {@link #text()}.
   * 
   * @return The internal English alphabet text message.
   */
  @Override
  public String toString()
  {
    return _text;
  }
}